package com.autumn.zero.authorization.credential;

import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.autumn.auditing.IClientInfoProvider;
import com.autumn.runtime.session.claims.IdentityClaims;
import com.autumn.security.AutumnAccountCredentialsException;
import com.autumn.security.auth.AutumnAuthenticationToken;
import com.autumn.security.auth.ExternalProviderToken;
import com.autumn.security.auth.UserNamePasswordToken;
import com.autumn.security.constants.UserStatusConstants;
import com.autumn.security.user.DefaultAutumnUser;
import com.autumn.security.user.IAutumnUser;
import com.autumn.security.user.IAutumnUserCredentialsService;
import com.autumn.util.AutoMapUtils;
import com.autumn.util.StringUtils;
import com.autumn.validation.MatchesUtils;
import com.autumn.zero.authorization.constants.LoginResultConstants;
import com.autumn.zero.authorization.entitys.roles.AbstractRole;
import com.autumn.zero.authorization.entitys.users.AbstractUser;
import com.autumn.zero.authorization.entitys.users.UserLoginAttempt;
import com.autumn.zero.authorization.entitys.users.UserLoginLog;
import com.autumn.zero.authorization.entitys.users.querys.UserClaimQuery;
import com.autumn.zero.authorization.entitys.users.querys.UserPermissionQuery;
import com.autumn.zero.authorization.services.IAuthorizationServiceBase;

/**
 * 抽象的用户认证服务
 * 
 * @author 杨昌国 2018-12-08 13:40:11
 * @param <TUser> 用户类型
 * @param <TRole> 角色类型
 */
public class UserCredentialsService<TUser extends AbstractUser, TRole extends AbstractRole>
		implements IAutumnUserCredentialsService {

	/**
	 * 日志
	 */
	protected final Log logger = LogFactory.getLog(this.getClass());

	/**
	 * 获取授权服务
	 * 
	 * @return
	 */
	protected final IAuthorizationServiceBase<TUser, TRole> authorizationService;

	/**
	 * 客户端信息提供程序
	 */
	protected final IClientInfoProvider clientInfoProvider;

	/**
	 * 
	 * @param authorizationService
	 * @param clientInfoProvider
	 */
	public UserCredentialsService(IAuthorizationServiceBase<TUser, TRole> authorizationService,
			IClientInfoProvider clientInfoProvider) {
		this.authorizationService = authorizationService;
		this.clientInfoProvider = clientInfoProvider;
	}

	@Override
	public IAutumnUser loadUserByToken(AutumnAuthenticationToken token) throws AutumnAccountCredentialsException {
		IAutumnUser user;
		if (token instanceof UserNamePasswordToken) {
			user = this.loadByUserNamePassword((UserNamePasswordToken) token);
		} else if (token instanceof ExternalProviderToken) {
			user = this.loadByExternalProvider((ExternalProviderToken) token);
		} else {
			throw new AutumnAccountCredentialsException("不支持的认证类型:" + token.getClass().getName());
		}
		this.loadUserPermission(user);
		this.loadUserClaim(user);
		return user;
	}

	/**
	 * 加载权限
	 * 
	 * @param user
	 */
	protected void loadUserPermission(IAutumnUser user) {
		List<String> userPermissions = user.getPermissions();
		if (userPermissions == null) {
			return;
		}
		List<UserPermissionQuery> permissions = this.authorizationService.queryUserAllPermissions(user.getId());
		for (UserPermissionQuery permission : permissions) {
			if (permission.getIsGranted()) {
				if (StringUtils.isNullOrBlank(permission.getName())) {
					userPermissions.add(permission.getResourcesId().toLowerCase());
				} else {
					String permissionName = String.format("%s:%s", permission.getResourcesId(), permission.getName());
					userPermissions.add(permissionName.toLowerCase());
				}
			}
		}
	}

	/**
	 * 加载声明
	 * 
	 * @param user
	 */
	protected void loadUserClaim(IAutumnUser user) {
		IdentityClaims identityClaims = user.getIdentityClaims();
		if (identityClaims == null) {
			return;
		}
		List<UserClaimQuery> claims = this.authorizationService.queryUserAllClaims(user.getId());
		for (UserClaimQuery claim : claims) {
			identityClaims.put(claim.getClaimType().toLowerCase(), claim.getClaimValue());
		}
	}

	/**
	 * 
	 * @param user
	 * @param account
	 * @param provider
	 * @param providerKey
	 * @param identityType
	 * @return
	 */
	protected IAutumnUser createAutumnUser(TUser user, String account, String provider, String providerKey,
			String identityType) {
		switch (user.getStatus()) {
		case UserStatusConstants.LOCKING:
			this.addUserLoginAttempt(user.getId(), account, provider, providerKey,
					LoginResultConstants.ACCOUNT_LOCKING);
			this.throwError("账户[" + user.getUserName() + "]已锁定", "账户已锁定");
			break;
		case UserStatusConstants.NOT_ACTIVATE:
			this.addUserLoginAttempt(user.getId(), account, provider, providerKey, LoginResultConstants.NOT_ACTIVATE);
			this.throwError("账户[" + user.getUserName() + "]未激活", "账户未激活");
			break;
		case UserStatusConstants.EXPIRED:
			this.addUserLoginAttempt(user.getId(), account, provider, providerKey,
					LoginResultConstants.ACCOUNT_EXPIRED);
			this.throwError("账户[" + user.getUserName() + "]已过期", "账户已过期");
			break;
		default:
			break;
		}
		List<TRole> roles = this.authorizationService.queryUserByRoles(user.getId());
		DefaultAutumnUser autumnUser = new DefaultAutumnUser();
		autumnUser.setIdentityType(identityType);
		autumnUser.setId(user.getId());
		autumnUser.setPassword(user.getPassword());
		autumnUser.setStatus(user.getStatus());
		autumnUser.setUserName(user.getUserName());
		for (TRole role : roles) {
			autumnUser.getRoles().add(role.getName().toLowerCase());
		}
		return autumnUser;
	}

	/**
	 * 用户名与密码登录
	 * 
	 * @param token
	 * @return
	 */
	protected IAutumnUser loadByUserNamePassword(UserNamePasswordToken token) {
		TUser user = this.authorizationService.findUserByAccount(token.getUsername(), false);
		String username = token.getUsername().trim();
		if (user == null) {
			this.addUserLoginAttempt(null, username, "", "", LoginResultConstants.INVALID_USER_ACCOUNT);
			this.throwError("账户:" + username + "无效", "账户或密码出错");
		}
		if (!user.getIsActivatePhone() && MatchesUtils.isMobilePhone(username)) {
			this.addUserLoginAttempt(null, username, "", "", LoginResultConstants.NOT_ACTIVATE);
			this.throwError("手机号:" + username + " 未激活", "手机号未激活");
		}
		if (!user.getIsActivateEmail() && MatchesUtils.isEmail(username)) {
			this.addUserLoginAttempt(null, username, "", "", LoginResultConstants.NOT_ACTIVATE);
			this.throwError("邮箱:" + username + " 未激活", "邮箱未激活");
		}
		return this.createAutumnUser(user, username, "", "", token.getIdentityType());
	}

	/**
	 * 第三方登录
	 * 
	 * @param token 票据
	 * @return
	 */
	protected IAutumnUser loadByExternalProvider(ExternalProviderToken token) {
		TUser user = this.authorizationService.findUserByExternalProvider(token.getProvider(), token.getProviderKey(),
				false);
		if (user == null) {
			this.addUserLoginAttempt(null, "", token.getProvider(), token.getProviderKey(),
					LoginResultConstants.INVALID_EXTERNAL_PROVIDER);
			this.throwError("无效的第三方提供程序[" + token.getProvider() + " " + token.getProviderKey() + "]", "无效的第三方提供程序或键");
		}
		return this.createAutumnUser(user, "", token.getProvider(), token.getProviderKey(), token.getIdentityType());
	}

	private void throwError(String msg, String throwMsg) {
		logger.error(msg + ",无法登录,IP地址:" + this.clientInfoProvider.getClientIpAddress() + " 客户信息:"
				+ this.clientInfoProvider.getBrowserInfo().getBrowserInfo());
		throw new AutumnAccountCredentialsException(throwMsg);
	}

	private void addUserLoginAttempt(Long userId, String account, String provider, String providerKey,
			int loginResult) {
		UserLoginAttempt attempt = createAttempt();
		attempt.setUserId(userId);
		attempt.setUserAccount(account);
		attempt.setProvider(provider);
		attempt.setProviderKey(providerKey);
		attempt.setLoginResult(loginResult);
		this.authorizationService.addUserLoginAttempt(attempt);
	}

	private UserLoginAttempt createAttempt() {
		UserLoginAttempt attempt = new UserLoginAttempt();
		attempt.setBrowserInfo(this.clientInfoProvider.getBrowserInfo().getBrowserInfo());
		attempt.setClientIpAddress(this.clientInfoProvider.getClientIpAddress());
		attempt.setClientName(this.clientInfoProvider.getClientPlatformName());
		return attempt;
	}

	@Override
	public void credentiaMatchResult(AutumnAuthenticationToken token, IAutumnUser user, boolean isMatch) {
		String account = "";
		String provider = "";
		String providerKey = "";
		if (token instanceof UserNamePasswordToken) {
			account = ((UserNamePasswordToken) token).getUsername();
		}
		if (token instanceof ExternalProviderToken) {
			ExternalProviderToken epToken = (ExternalProviderToken) token;
			provider = epToken.getProvider();
			providerKey = epToken.getProviderKey();
		}
		UserLoginAttempt attempt = createAttempt();
		attempt.setUserId(user.getId());
		attempt.setUserAccount(account);
		attempt.setProvider(provider);
		attempt.setProviderKey(providerKey);
		if (isMatch) {
			UserLoginLog log = new UserLoginLog();
			AutoMapUtils.map(attempt, log);
			attempt.setLoginResult(LoginResultConstants.SUCCESS);
			this.authorizationService.addUserLoginAttempt(attempt);
			this.authorizationService.addUserLoginLog(log);
		} else {
			attempt.setLoginResult(LoginResultConstants.INVALID_USER_ACCOUNT_OR_PASSWORD);
			this.authorizationService.addUserLoginAttempt(attempt);
		}
	}
}
